package me.yricky.alia.utils.gds

import me.yricky.alia.data.base.Coord
import me.yricky.alia.data.shape.Polygon
import me.yricky.alia.data.shape.Shape
import java.nio.ByteBuffer
import java.util.BitSet
import kotlin.math.pow

/**
 * @author Yricky
 * @date 2022/1/28
 */
object GDS {
    sealed class Record(
        val length:Short,
        val type: RecordType,
    ){
        open class Raw(
            length:Short,
            type: RecordType,
            val content:ByteArray
        ): Record(length, type)

        class LabelRecord(type: RecordType):Record(4,type)

        class TimeRecord(
            length:Short,
            type: RecordType,
            content:ByteArray
        ): Raw(length, type, content){
            val modifyTime: Time
            val accessTime: Time
            init {
                val bb = ByteBuffer.wrap(content)
                if(length != 0x1c.toShort()){
                    modifyTime = Time()
                    accessTime = Time()
                }else{
                    modifyTime = Time(bb.short,bb.short,bb.short,bb.short,bb.short,bb.short)
                    accessTime = Time(bb.short,bb.short,bb.short,bb.short,bb.short,bb.short)
                }
            }
            class Time(
                val yy:Short=-1,
                val MM:Short=-1,
                val dd:Short=-1,
                val hh:Short=-1,
                val mm:Short=-1,
                val ss:Short=-1
            ){
                override fun toString(): String {
                    return "$yy-$MM-$dd,$hh:$mm:$ss"
                }
            }
        }

        class StringRecord(length: Short,type: RecordType,content: ByteArray):Record(
            length, type
        ){
            val name = String(content,0,
                if(content.last() == 0.toByte()) content.size-1 else content.size
            )
        }

        class Units(length: Short,content: ByteArray): Record(
            length, RecordType.UNITS
        ){
            val unitInUserUnit : Double
            val unitInMeter : Double
            init {
                val bb = ByteBuffer.wrap(content)
                unitInUserUnit = bb.double()
                unitInMeter = bb.double()
            }

        }

        class IdRecord(length: Short,type: RecordType,content: ByteArray):Record(
            length, type
        ){
            val id:Short = ByteBuffer.wrap(content).short
        }

        class XY(length: Short,content: ByteArray): Record(
            length, RecordType.XY
        ){
            val shape: Shape by lazy{
                val dataLen = (length-4)/8
                val bb = ByteBuffer.wrap(content)
                val list:MutableList<Coord> = ArrayList(dataLen)
                (0 until dataLen).forEach {
                    list.add(Coord(bb.int,bb.int))
                }
                val _shape = Polygon(list)
                if(
                    ((list.size == 4) || (list.size == 5 && list[0] == list[4])) &&
                    (
                            (
                                    ((list[0].x == list[1].x && list[2].x == list[3].x) &&
                                            (list[0].y == list[3].y && list[1].y == list[2].y))
                                    ) || (
                                    ((list[0].y == list[1].y && list[2].y == list[3].y) &&
                                            (list[0].x == list[3].x && list[1].x == list[2].x))
                                    )
                            )
                ){
                    _shape.bBox
                }else{
                    _shape
                }
            }
        }

        class Presentation(length: Short,content: ByteArray): Record(
            length, RecordType.PRESENTATION
        ){
            private val value = ByteBuffer.wrap(content).short
            enum class Align(val value:Int){
                No(-1),
                Start(0),
                Center(1),
                End(2)
            }
            enum class Font(val value: Int){
                NoFont(-1),
                DefaultFont(0),
                GothicFont(1),
                SansSerifFont(2),
                StickFont(3),
                TimesItalicFont(4),
                TimesThinFont(5),
                TimesFont(6),
                NFonts(7)
            }
            fun getHAlign(): Align {
                return Align.values().associateBy { it.value }[value.toInt() and 3] ?: Align.No
            }

            fun getVAlign(): Align {
                return Align.values().associateBy { it.value }[value.toInt().shr(2).and(3)] ?: Align.No
            }

            fun getFont(): Font {
                return Font.values().associateBy { it.value }[value.toInt().shr(4).and(7)] ?: Font.NoFont
            }

        }

        class STrans(length: Short, content: ByteArray):Record(length,RecordType.STRANS){
            private val set = BitSet.valueOf(content)
            val reflection = set[15]
            val mag = set[2]
            val angle = set[1]
        }

        class Mag(length: Short, content: ByteArray):Record(length,RecordType.MAG){
            val double = ByteBuffer.wrap(content).double()
        }

        class Angle(length: Short, content: ByteArray):Record(length,RecordType.ANGLE){
            val double = ByteBuffer.wrap(content).double()
        }

        class ColRow(length: Short, content: ByteArray):Record(length,RecordType.COLROW){
            val col:Short
            val row:Short
            init {
                val bb = ByteBuffer.wrap(content)
                col = bb.short.coerceAtLeast(1)
                row = bb.short.coerceAtLeast(1)
            }
        }

        object Invalid: Raw(0, RecordType.INVALID,ByteArray(0))

        companion object{
            private fun ByteBuffer.double():Double{
                val l0 = int.toUInt()
                val l1 = int.toUInt()
                var x = 4294967296.0 * (l0 and 0xffffff.toUInt()).toDouble() + (l1 and 0xffffffff.toUInt()).toDouble()
                if((l0 and 0x80000000.toUInt()) != 0.toUInt()){
                    x = -x
                }
                val p = (l0 and 0x7f000000.toUInt()).shr(24) - (64 + 14).toUInt()
                if(p != 0.toUInt()){
                    x *= (16.0).pow(p.toInt())
                }
                return x
            }

            fun parse(rec: Raw): Record {
                return when(rec.type){
                    RecordType.BOUNDARY,
                    RecordType.TEXT,
                    RecordType.SREF,
                    RecordType.AREF,
                    RecordType.ENDEL,
                    RecordType.ENDLIB,
                    RecordType.ENDSTR -> LabelRecord(rec.type)
                    RecordType.BGNLIB,
                    RecordType.BGNSTR -> TimeRecord(rec.length,rec.type,rec.content)
                    RecordType.LIBNAME,
                    RecordType.STRNAME,
                    RecordType.STRING,
                    RecordType.SNAME,
                    RecordType.PROPVALUE -> StringRecord(rec.length,rec.type,rec.content)
                    RecordType.UNITS ->  Units(rec.length,rec.content)
                    RecordType.LAYER,
                    RecordType.DATATYPE,
                    RecordType.TEXTTYPE,
                    RecordType.PROPATTR,
                    RecordType.HEADER -> IdRecord(rec.length,rec.type,rec.content)
                    RecordType.STRANS ->STrans(rec.length,rec.content)
                    RecordType.MAG -> Mag(rec.length,rec.content)
                    RecordType.ANGLE -> Angle(rec.length,rec.content)
                    RecordType.XY -> XY(rec.length,rec.content)
                    RecordType.COLROW -> ColRow(rec.length,rec.content)
                    RecordType.PRESENTATION -> Presentation(rec.length,rec.content)
                    else -> rec
                }
            }
        }
    }

    enum class RecordType(val value:Short){
        INVALID     (-1    ),
        HEADER      (0x0002),
        BGNLIB      (0x0102),
        LIBNAME     (0x0206),
        UNITS       (0x0305),
        ENDLIB      (0x0400),
        BGNSTR      (0x0502),
        STRNAME     (0x0606),
        ENDSTR      (0x0700),
        BOUNDARY    (0x0800),
        PATH        (0x0900),
        SREF        (0x0a00),
        AREF        (0x0b00),
        TEXT        (0x0c00),
        LAYER       (0x0d02),
        DATATYPE    (0x0e02),
        WIDTH       (0x0f03),
        XY          (0x1003),
        ENDEL       (0x1100),
        SNAME       (0x1206),
        COLROW      (0x1302),
        TEXTNODE    (0x1400),
        NODE        (0x1500),
        TEXTTYPE    (0x1602),
        PRESENTATION(0x1701),
        STRING      (0x1906),
        STRANS      (0x1a01),
        MAG         (0x1b05),
        ANGLE       (0x1c05),
        REFLIBS     (0x1f06),
        FONTS       (0x2006),
        PATHTYPE    (0x2102),
        GENERATIONS (0x2202),
        ATTRTABLE   (0x2306),
        STYPTABLE   (0x2406),
        STRTYPE     (0x2502),
        ELFLAGS     (0x2601),
        ELKEY       (0x2703),
        NODETYPE    (0x2a02),
        PROPATTR    (0x2b02),
        PROPVALUE   (0x2c06),
        BOX         (0x2d00),
        BOXTYPE     (0x2e02),
        PLEX        (0x2f03),
        BGNEXTN     (0x3003),
        ENDEXTN     (0x3103),
        TAPENUM     (0x3202),
        TAPECODE    (0x3302),
        STRCLASS    (0x3401),
        RESERVED    (0x3503),
        FORMAT      (0x3602),
        MASK        (0x3706),
        ENDMASKS    (0x3800),
        LIBDIRSIZE  (0x3902),
        SRFNAME     (0x3a06),
    }
    private val types:Map<Short, RecordType> by lazy {
        RecordType.values().associateBy { it.value }
    }
    fun recordType(value: Short): RecordType {
        return types[value] ?: RecordType.INVALID
    }
}